<!--
 * @Description: 代码编辑器
-->
<template>
  <div :class="['_ehl_code', { _ehl_code_big: big }]">
    <div
      v-if="tool['show']"
      :class="['_code_tool', { _code_tool_right: theme === 'light' }]"
    >
      <div
        class="iconn el-icon-d-caret"
        :style="{ 'font-size': '16px' }"
        :title="$t('common.zuixiaohua')"
        v-if="big === true && tool['max_min']"
        @click="changeSmall"
      />
      <div
        class="iconn el-icon-d-caret"
        :style="{ 'font-size': '16px' }"
        :title="$t('common.zuidahua')"
        v-if="big === false && tool['max_min']"
        @click="changeBig"
      />
      <div
        v-if="tool['format']"
        class="iconn el-icon-magic-stick"
        :style="{ 'font-size': '16px' }"
        :title="$t('common.geshihua')"
        @click="format"
      />
      <!-- <div class="iconn el-icon-crop" title="获取选中" @click="getSelection" /> -->
      <div
        v-if="tool['clearable']"
        class="iconn el-icon-delete"
        :style="{ 'font-size': '16px' }"
        :title="$t('common.qingkong')"
        @click="clearSql"
      />
      <ehl-select
        v-if="tool['mode']"
        :class="['select', { select_light: theme === 'light' }]"
        v-model="select.value"
        :options="select.options"
        :prop="select.prop"
        :clearable="false"
        @change="onChange"
      />
    </div>
    <textarea ref="editor" />
    <div
      :class="['_code_error', { _code_error_right: theme === 'light' }]"
      v-if="error !== ''"
      >{{ error }}</div
    >
  </div>
</template>
<script>
  import CodeMirror from 'codemirror'
  import 'codemirror/lib/codemirror.css'

  //sql格式化
  import { format } from 'sql-formatter'

  //不同的模式
  import 'codemirror/mode/sql/sql'
  import 'codemirror/mode/javascript/javascript'
  import 'codemirror/mode/yaml-frontmatter/yaml-frontmatter'
  import 'codemirror/mode/xml/xml'
  import '@/common/utils/lib/freemarker'
  import '@/common/utils/lib/freemarkermixed'

  //代码提示插件
  import 'codemirror/addon/hint/show-hint.css'
  import 'codemirror/addon/hint/show-hint.js'
  import '@/common/utils/lib/sql-hint.js'
  import 'codemirror/addon/hint/javascript-hint.js'

  import 'codemirror/addon/edit/matchbrackets.js'
  import 'codemirror/addon/fold/xml-fold.js'

  //搜索
  import 'codemirror/addon/scroll/annotatescrollbar.js'
  import 'codemirror/addon/search/matchesonscrollbar.js'
  import 'codemirror/addon/search/match-highlighter.js'
  import 'codemirror/addon/search/jump-to-line.js'
  import 'codemirror/addon/dialog/dialog.js'
  import 'codemirror/addon/dialog/dialog.css'
  import 'codemirror/addon/search/searchcursor.js'
  import 'codemirror/addon/search/search.js'

  //折叠
  import 'codemirror/addon/fold/foldgutter.css'
  import 'codemirror/addon/fold/foldcode'
  import 'codemirror/addon/fold/foldgutter'
  import 'codemirror/addon/fold/brace-fold'
  import 'codemirror/addon/fold/comment-fold'

  //主题
  import 'codemirror/theme/blackboard.css'
  import 'codemirror/theme/idea.css'

  let modeMap = {
      //模式类型
      sql: 'text/x-sql',
      javascript: 'javascript',
      json: 'application/json',
      xml: 'application/xml',
      yaml: 'yaml',
      freemarker: 'freemarker',
    },
    themeOpt = {
      //主题类型
      light: 'idea',
      dark: 'blackboard',
    }

  export default {
    props: {
      value: { type: String, default: () => '' },
      //默认模式,参考modeMap
      mode: { type: String, default: () => 'sql' },
      //主题选择 light dark
      theme: { type: String, default: () => 'light' },
      //高度
      height: { type: Number, default: () => 150 },
      //sql的额外提示
      hits: { type: Object, default: () => ({}) },
      //是否只读
      readonly: { type: Boolean, default: () => false },
      //是否一开始格式化
      autoFormat: { type: Boolean, default: () => false },
      //显示哪些工具条
      tool: {
        type: Object,
        default: () => ({
          show: true, //总体是否显示
          max_min: true, //是否显示最大化
          format: true, //是否显示格式化
          clearable: true, //是否显示清空
          mode: true, //是否显示模式选择器
          lineNum: true, //是否显示行号
        }),
      },
    },
    components: {
      ehlSelect: () => $import('ehlSelect'),
    },
    data() {
      return {
        editor: null,
        big: false,
        select: {
          value: { id: this.mode, mode: modeMap[this.mode] },
          options: Object.keys(modeMap).map(key => {
            return { id: key, mode: modeMap[key] }
          }),
          prop: { label: 'mode', value: 'id' },
        },
        options: {
          readOnly: this.readonly,
          // styleActiveLine: true, // 当前行背景高亮
          // matchBrackets: true, // 括号匹配
          lineWrapping: true, // 自动换行
          // gutters:
          //   this.tool.show && this.tool.lineNum
          //     ? [
          //         'CodeMirror-linenumbers',
          //         'CodeMirror-foldgutter',
          //         'CodeMirror-lint-markers',
          //       ]
          //     : [],
          extraKeys: { Ctrl: 'autocomplete' }, //自动提示配置
          theme: themeOpt[this.theme], //选中的theme
          lineNumbers: this.tool.show && this.tool.lineNum, //是否显示行号
          firstLineNumber: 1, //第一行行号开始数值
          lineNumberFormatter: num => {
            return `${num}`
          }, //格式化行号显示内容
          showCursorWhenSelecting: false, //选中文字后是否显示光标
          dragDrop: true, //是否支持文字拖拽移位
          cursorHeight: 0.8, //光标高度
          resetSelectionOnContextMenu: false, //右键菜单是否更改光标位置
          hintOptions: {
            table: { 'hive.table': ['filed'] },
            completeSingle: false,
            // hint: (cm, callback, options) => {
            // const cur = cm.getCursor()
            // const txt = cm.getTokenAt(cur).string
            // return {
            //   list: ['<#list', 'asd', 'aed'],
            //   from: { line: cur.line, ch: cur.ch - txt.length, sticky: null },
            //   to: { line: cur.line, ch: cur.ch, sticky: null },
            // }
            // },
          },
          // spellcheck: true,
          // autocorrect: true,
        },
        error: '',
      }
    },
    mounted() {
      this.initCodeMirror()
      this.modeLoad(this.mode)
      this.autoFormat && this.format()
    },
    watch: {
      value(val, old) {
        let cur = this.editor.getCursor()
        this.editor.setValue(val)
        this.editor.setCursor(cur)
        // this.editor.closeHint()
      },
      hits: {
        handler: function (val, old) {
          this.options.hintOptions.table = val
          this.editor.setOption('hintOptions', this.options.hintOptions)
        },
        deep: true,
      },
    },
    methods: {
      changeSmall() {
        this.big = false
        setTimeout(() => {
          this.editor.refresh()
        }, 1)
      },
      changeBig() {
        this.big = true
        setTimeout(() => {
          this.editor.refresh()
        }, 1)
      },
      initCodeMirror() {
        this.options.hintOptions.table = this.hits
        this.options.mode = this.select.value.mode
        this.editor = CodeMirror.fromTextArea(this.$refs.editor, this.options)
        this.editor.setValue(this.value || '')
        this.editor.on('change', editor => {
          this.error = ''
          this.$emit('input', editor.getValue())
          this.$emit('change', editor.getValue())
        })
        this.editor.on('keyup', (codeMirror, event) => {
          let arr = ['ArrowRight', 'ArrowLeft', 'ArrowDown', 'ArrowUp', 'Space'] //排除列表
          if (arr.indexOf(event.code) === -1) {
            this.editor.showHint()
          }
        })
        // this.editor.on('inputRead', () => {
        //   this.editor.showHint()
        // })
        this.editor.on('keydown', (codeMirror, event) => {
          let arr = ['Space'] //关闭列表
          if (arr.indexOf(event.code) > -1) {
            this.editor.closeHint()
          }
        })
        this.editor.on('mousedown', () => {
          this.editor.closeHint()
        })
        this.$nextTick(() => {
          let tmp = document.querySelectorAll('.CodeMirror')
          for (const iterator of tmp) {
            iterator.style.height = `${this.height}px`
          }
        })
      },
      modeLoad(mode) {
        // if (mode === 'sql') {
        //   require('codemirror/mode/sql/sql.js')
        //   require('@/common/utils/lib/sql-hint.js')
        // } else if (mode === 'javascript' || mode === 'json') {
        //   require('codemirror/mode/javascript/javascript.js')
        //   require('codemirror/addon/hint/javascript-hint.js')
        // } else if (mode === 'xml') {
        //   require('codemirror/mode/xml/xml.js')
        //   require('codemirror/addon/edit/matchbrackets.js')
        //   require('codemirror/addon/fold/xml-fold.js')
        // } else if (mode === 'yaml') {
        //   require('codemirror/mode/yaml-frontmatter/yaml-frontmatter.js')
        // } else if (mode === 'freemarker') {
        //   require('@/common/utils/lib//freemarker.js')
        //   require('@/common/utils/lib//freemarkermixed.js')
        // }
        this.editor.setOption('mode', modeMap[mode])
        // this.autoFormat && this.format()
      },
      onChange(val) {
        this.modeLoad(val.id)
      },
      getSelection() {
        return this.editor.getSelection()
      },
      getSql() {
        return this.value
      },
      format() {
        try {
          let txt = this.editor.getValue()
          if (!isEmpty(txt)) {
            if (this.select.value.mode.indexOf('sql') > -1) {
              this.editor.setValue(format(txt))
              this.$emit('input', format(txt))
            } else if (this.select.value.mode.indexOf('json') > -1) {
              this.editor.setValue(this.formatJson(txt))
              this.$emit('input', this.formatJson(txt))
            } else if (this.select.value.mode.indexOf('xml') > -1) {
              this.editor.setValue(this.formatXml(txt))
              this.$emit('input', this.formatXml(txt))
            } else if (this.select.value.mode.indexOf('yaml') > -1) {
              this.editor.setValue(this.formatYaml(txt))
              this.$emit('input', this.formatYaml(txt))
            }
          }
        } catch (error) {
          this.error = error
        }
      },
      getSelect() {
        return this.editor.getSelection()
      },
      clearSql() {
        this.editor.setValue('')
        this.$emit('input', '')
      },
      formatJson(txt) {
        var tmpjson
        eval(`tmpjson = ${txt}`)
        let prettyjson = require('json-format')
        let tmp = prettyjson(tmpjson, {
          type: 'space',
          size: 2,
        })
        return tmp
      },
      formatXml(text) {
        let _this = this
        //去掉多余的空格
        text =
          '\n' +
          text
            .replace(/(<\w+)(\s.*?>)/g, function ($0, name, props) {
              return name + ' ' + props.replace(/\s+(\w+=)/g, ' $1')
            })
            .replace(/>\s*?</g, '>\n<')
        //把注释编码
        text = text
          .replace(/\n/g, '\r')
          .replace(/<!--(.+?)-->/g, function ($0, text) {
            let ret = '<!--' + escape(text) + '-->'
            //alert(ret);
            return ret
          })
          .replace(/\r/g, '\n')
        //调整格式
        let rgx =
          /\n(<(([^?]).+?)(?:\s|\s*?>|\s*?(\/)>)(?:.*?(?:(?:(\/)>)|(?:<(\/)\2>)))?)/gm
        let nodeStack = []
        let output = text.replace(
          rgx,
          function (
            $0,
            all,
            name,
            isBegin,
            isCloseFull1,
            isCloseFull2,
            isFull1,
            isFull2,
          ) {
            let isClosed =
              isCloseFull1 == '/' ||
              isCloseFull2 == '/' ||
              isFull1 == '/' ||
              isFull2 == '/'
            //alert([all,isClosed].join('='));
            let prefix = ''
            if (isBegin == '!') {
              prefix = _this.getPrefix(nodeStack.length)
            } else {
              if (isBegin != '/') {
                prefix = _this.getPrefix(nodeStack.length)
                if (!isClosed) {
                  nodeStack.push(name)
                }
              } else {
                nodeStack.pop()
                prefix = _this.getPrefix(nodeStack.length)
              }
            }
            let ret = '\n' + prefix + all
            return ret
          },
        )
        let prefixSpace = -1
        let outputText = output.substring(1)
        //alert(outputText);
        //把注释还原并解码，调格式
        outputText = outputText
          .replace(/\n/g, '\r')
          .replace(/(\s*)<!--(.+?)-->/g, function ($0, prefix, text) {
            //alert(['[',prefix,']=',prefix.length].join(''));
            if (prefix.charAt(0) == '\r') prefix = prefix.substring(1)
            text = unescape(text).replace(/\r/g, '\n')
            let ret = '\n' + prefix + '<!--' + text.replace(/^\s*/gm, prefix) + '-->'
            //alert(ret);
            return ret
          })

        return outputText.replace(/\s+$/g, '').replace(/\r/g, '\r\n')
      },
      formatYaml(text) {
        return text
      },
      //Xml格式化使用
      getPrefix(prefixIndex) {
        let span = '  '
        let output = []
        for (let i = 0; i < prefixIndex; ++i) {
          output.push(span)
        }
        return output.join('')
      },
    },
  }
</script>
